/*

Miranda IM: the free IM client for Microsoft* Windows*

Copyright 2000-2007 Miranda ICQ/IM project, 
all portions of this codebase are copyrighted to the people 
listed in contributors.txt.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or ( at your option ) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/
#include "commonheaders.h"
#include "classTzManager.h"
#include "ctrl.h"

/**************************************************************************************************
 * struct CTimeZone
 **************************************************************************************************/

/**
 * This is the default constructure, which resets
 * all attributes to NULL.
 **/
CTimeZone::CTimeZone()
{
	memset( this, 0, sizeof( *this ) );
}

/**
 * The default construcor's task ist to clear out
 * all pieces of the used memory.
 **/
CTimeZone::~CTimeZone()
{
	if( pszDisplay )
	{
		mir_free( pszDisplay );
		pszDisplay = NULL;
	}
}

/**
 * This method can be used to basically convert a Windows
 * timezone to the format, known by miranda.
 *
 * @warning	This operation does not work vice versa in
 *					all cases, as there are sometimes more then
 *					one Windows timezones with the same Bias.
 **/
BYTE CTimeZone::ToMirandaTimezone() const
{
	return ( BYTE ) ( Bias / 30 );
}

/**
 * This operator translates the content of this object to
 * a TIME_ZONE_INFORMATION structure as it is required by 
 * several windows functions.
 **/
CTimeZone::operator TIME_ZONE_INFORMATION() const
{
	TIME_ZONE_INFORMATION tzi;

	tzi.Bias = Bias;
	tzi.DaylightBias = DaylightBias;
	tzi.StandardBias = StandardBias;

	memcpy( &tzi.DaylightDate, &DaylightDate, sizeof( DaylightDate ) );
	memcpy( &tzi.StandardDate, &StandardDate, sizeof( DaylightDate ) );
	//memset( tzi.DaylightName, 0, sizeof( tzi.DaylightName ) );
	//memset( tzi.StandardName, 0, sizeof( tzi.StandardName ) );

	return tzi;
}		

/***********************************************************************************************************
 * class CTzBiasIdx
 ***********************************************************************************************************/

INT	CTzByBias::sortFunc( const CTimeZone *tz1, const CTimeZone *tz2 )
{
	return ( tz2->Bias < tz1->Bias )
		? -1 : ( tz2->Bias > tz1->Bias ? 1 : mir_tcscmp( tz1->pszDisplay, tz2->pszDisplay ) ) ;	
}

CTzByBias::CTzByBias() : LIST<CTimeZone>( 50, ( FTSortFunc ) CTzByBias::sortFunc )
{
}

CTzByBias::~CTzByBias()
{
	destroy();
}

CTimeZone* CTzByBias::find( LONG Bias, LPTSTR pszDisplay ) const
{
	CTimeZone key;
	key.Bias = Bias;
	key.pszDisplay = pszDisplay;
	return find( &key );
}

/***********************************************************************************************************
 * class CTzByIndex
 ***********************************************************************************************************/

INT	CTzByIndex::sortFunc( const CTimeZone *tz1, const CTimeZone *tz2 )
{
	return tz1->dwIndex < tz2->dwIndex ? -1 : tz1->dwIndex > tz2->dwIndex;	
}

CTzByIndex::CTzByIndex() : LIST<CTimeZone>( 50, ( FTSortFunc ) CTzByIndex::sortFunc )
{
}

CTzByIndex::~CTzByIndex()
{
	destroy();
}

/**
 * This method clears the TzManager's data.
 **/		
VOID CTzByIndex::destroy( VOID )
{
	INT i;

	// delete data
	for( i = 0; i < getCount(); i++ )
	{
		delete (*this)[i];
	}
	// delete the list
	LIST<CTimeZone>::destroy();
}

CTimeZone* CTzByIndex::find( DWORD idx ) const
{
	if( idx != ( DWORD ) -1 )
	{	
		CTimeZone key;
		key.dwIndex = idx;
		return find( &key );
	}
	return NULL;
}

/***********************************************************************************************************
 * class CTzManager
 ***********************************************************************************************************/

/**
 *
 *
 **/
CTzManager::CTzManager()
	: _bias(), Bias( _bias ),
		_index(), Index( _index )
{
}

/**
 * This method loads all information about timezones from windows' registry.
 **/
INT CTzManager::GetTimeZonesFromRegistry()
{
	INT		result;
	HKEY	hKeyRoot, hKeyTz;
	DWORD	i, cbData;
	TCHAR szSubKey[MAX_PATH],
				szDisplay[MAX_PATH];

	result = RegOpenKey( HKEY_LOCAL_MACHINE, 
						_T("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones"),
						&hKeyRoot );

	if( result == ERROR_SUCCESS )
	{
		// clear out old list
		_index.destroy();
		_bias.destroy();

		for( i = 0; ERROR_SUCCESS == RegEnumKey( hKeyRoot, i, szSubKey, SIZEOF( szSubKey ) ); i++ )
		{
			result = RegOpenKey( hKeyRoot, szSubKey, &hKeyTz );
			if( result == ERROR_SUCCESS )
			{
				CTimeZone	*tz = new CTimeZone();

				if( tz )
				{
					cbData = sizeof( REG_TIME_ZONE_INFORMATION );
					result |= RegQueryValueEx( hKeyTz, _T("TZI"), 0, 0, ( LPBYTE ) tz, &cbData );
					
					cbData = sizeof( DWORD );
					result |= RegQueryValueEx( hKeyTz, _T("Index"), 0, 0, ( LPBYTE ) &tz->dwIndex, &cbData );

					cbData = sizeof( szDisplay );
					result |= RegQueryValueEx( hKeyTz, _T("Display"), 0, 0, ( LPBYTE ) szDisplay, &cbData );

					if( !result && _index.insert( tz ) )
					{
						tz->pszDisplay = mir_tcsdup( szDisplay );
						_bias.insert( tz );
					}
					else
					{
						delete tz;
						tz = NULL;
					}
				}
				RegCloseKey( hKeyTz );
			}
		}
		RegCloseKey( hKeyRoot );
	}
	return result;
}

/**
 * This method trys to find some default windows timezone idices for a given
 * miranda timezone.
 *
 * @param		MirTz		- this is a miranda timezone with values between -24 and 24.
 *
 * @return	This method returns the index of a windows timezone, which is maps
 *					the @MirTz value or -1 if no windows timezone index exists.
 **/
DWORD CTzManager::IndexFromMirandaTimeZone( const CHAR MirTz ) const
{
	/**
	 * This is an item of an array of timezones, which are known by both Miranda-IM
	 * and Windows. It is used to map an ICQ timezone against a Windows timezone
	 * for retrieving information about daylight saving time and more.
	 **/
	static const DWORD TzMap[] = {
		    0,  // GMT-12:00 Eniwetok; Kwajalein
		   -1,	// GMT-11:30
		    1,  // GMT-11:00 Midway Island; Samoa
		   -1,	// GMT-10:30
		    2,  // GMT-10:00 Hawaii
		   -1,	// GMT-9:30
		    3,  // GMT-9:00 Alaska
		   -1,	// GMT-8:30
		    4,  // GMT-8:00 Pacific Time; Tijuana
		   -1,	// GMT-7:30
		   15,  // GMT-7:00 Arizona; Mountain Time
		   -1,	// GMT-6:30
		   33,  // GMT-6:00 Central Time; Central America; Saskatchewan
		   -1,	// GMT-5:30
		   45,  // GMT-5:00 Eastern Time; Bogota; Lima; Quito
		   -1,	// GMT-4:30
		   57,  // GMT-4:00 Atlantic Time; Santiago; Caracas; La Paz
		   60,  // GMT-3:30 Newfoundland
		   70,  // GMT-3:00 Greenland; Buenos Aires; Georgetown
		   -1,	// GMT-2:30
		   75,  // GMT-2:00 Mid-Atlantic
		   -1,	// GMT-1:30
		   80,  // GMT-1:00 Cape Verde Islands; Azores
		   -1,	// GMT-0:30
		   85,  // GMT+0:00 London; Dublin; Edinburgh; Lisbon; Casablanca
		   -1,	// GMT+0:30
		  105,  // GMT+1:00 Central European Time; West Central Africa; Warsaw
		   -1,	// GMT+1:30
		  140,  // GMT+2:00 Jerusalem; Helsinki; Harare; Cairo; Bucharest; Athens
		   -1,	// GMT+2:30
		  145,  // GMT+3:00 Moscow; St. Petersburg; Nairobi; Kuwait; Baghdad
		  160,  // GMT+3:30 Tehran
		  165,  // GMT+4:00 Baku; Tbilisi; Yerevan; Abu Dhabi; Muscat
		  175,  // GMT+4:30 Kabul
		  185,  // GMT+5:00 Calcutta; Chennai; Mumbai; New Delhi; Ekaterinburg
		  200,  // GMT+5:30 Sri Jayawardenepura
		  201,  // GMT+6:00 Astana; Dhaka; Almaty; Novosibirsk
		  203,  // GMT+6:30 Rangoon
		  207,  // GMT+7:00 Bankok; Hanoi; Jakarta; Krasnoyarsk
		   -1,	// GMT+7:30
		  210,  // GMT+8:00 Perth; Taipei; Singapore; Hong Kong; Beijing
		   -1,	// GMT+8:30
		  235,  // GMT+9:00 Tokyo; Osaka; Seoul; Sapporo; Yakutsk
		  245,  // GMT+9:30 Darwin; Adelaide
		  270,  // GMT+10:00 East Australia; Guam; Vladivostok
		   -1,	// GMT+10:30
		  280,  // GMT+11:00 Magadan; Solomon Is.; New Caledonia
		   -1,	// GMT+11:30
		  290   // GMT+12:00 Auckland; Wellington; Fiji; Kamchatka; Marshall Is.
	};

	return ( MirTz >= -24 && MirTz <= 24 ) ? TzMap[ 24 - MirTz ] : -1 ;
}

/**
 * This method trys to find the index for the contact's windows timezone. If no index is 
 * available for the contact, the @e dwIndex value retrieves the bias of miranda's timezone value.
 *
 * @warning	Make sure you convert @e dwIndex to CHAR if the function returns 1 in order to get
 *					the correct miranda timezone!
 *
 * @param		hContact	- the HANDLE of the contact to read timezone information for
 * @param		szProto		- contact's protocol
 * @param		dwIndex		- This reference to an DWORD retrieves the index of the windows timezone,
 *											set for a contact, if the function returns 0. If the index can't be 
 *											determined, the variable retrieves the value for a miranda's 'timezone' setting
 *											or -100 (unspecified) and the function returns 1.
 *
 * @retval	tz		- The index for a windows timezone was found for the contact.
 * @retval	NULL	- There is no index, but if the contact's 'timezone' setting is valid, @e dwIndex
 *                  retrieved its value. If not, dwIndex is -100 (unspecified).
 **/
CTimeZone* CTzManager::GetContactTimeZone( HANDLE hContact, LPCSTR szProto, DWORD& dwIndex ) const
{
	CTimeZone *tz = NULL;

	dwIndex = DB::Setting::GetDWord( hContact, USERINFO, "TzIndex", -100 );
	if( dwIndex != ( DWORD )-100 )
  {
    tz = Index.find( dwIndex );
  }
	if( !tz )
	{
		DBVARIANT dbv;

		dbv.type = DBVT_DELETED;

		// try to get miranda's timezone value
		if( DB::Setting::GetAsIsEx( hContact, USERINFO, szProto, SET_CONTACT_TIMEZONE, &dbv ) || dbv.type != DBVT_BYTE )
		{
			// maybe a failure lets us read a string, so clear it out
			DB::Variant::Free( &dbv );
			dwIndex = ( DWORD ) -100;
		}
		else 
		{
			dwIndex = IndexFromMirandaTimeZone( dbv.cVal );
			tz = Index.find( dwIndex );
			if( !tz )
			{
				dwIndex = ( DWORD ) dbv.bVal;
			}
		}
	}
	return tz;
}

/**
 * This method trys to find the index for the contact's windows timezone. If no index is 
 * available for the contact, the @e dwIndex value retrieves the bias of miranda's timezone value.
 *
 * @warning	Make sure you convert @e dwIndex to CHAR if the function returns 1 in order to get
 *					the correct miranda timezone!
 *
 * @param		hContact	- the HANDLE of the contact to read timezone information for
 * @param		szProto		- contact's protocol
 * @param		dwIndex		- This reference to an DWORD retrieves the index of the windows timezone,
 *											set for a contact, if the function returns 0. If the index can't be 
 *											determined, the variable retrieves the value for a miranda's 'timezone' setting
 *											or -100 (unspecified) and the function returns 1.
 *
 * @retval	CTRLF_... flag	- The index for a windows timezone was found for the contact.
 * @retval	0								- There is no index, but if the contact's 'timezone' setting is valid,
 *														@e dwIndex retrieved its value.	If not, dwIndex is -100 (unspecified).
 **/
WORD CTzManager::GetContactTimeZoneIndexCtrl( HANDLE hContact, LPCSTR szProto, DWORD& dwIndex ) const
{
	WORD wFlags = 0;

	// try to read windows' timezone index from database
	dwIndex = DB::Setting::GetDWord( hContact, USERINFO, "TzIndex", -1 );
	if( dwIndex != ( DWORD )-1 )
	{
		wFlags = CTRLF_HASCUSTOM;
		if( !hContact || DB::Setting::Exists( hContact, szProto, SET_CONTACT_TIMEZONE ) )
		{
			wFlags |= CTRLF_HASPROTO;
		}
	}
	else
	{
		DBVARIANT dbv;

		dbv.type = DBVT_DELETED;

		// try to get miranda's timezone value
		wFlags = DB::Setting::GetAsIsCtrl( hContact, USERINFO, USERINFO, szProto, SET_CONTACT_TIMEZONE, &dbv );
		if( dbv.type == DBVT_BYTE )
		{
			CTimeZone key;
			key.dwIndex = IndexFromMirandaTimeZone( dbv.cVal );
			if( key.dwIndex != ( DWORD ) -1 && _index.find( &key ) )
			{
				dwIndex = key.dwIndex;
			}
			else
			{
				wFlags = 0;
			}
		}
		else
		{
			wFlags = 0;
			DB::Variant::Free( &dbv );
		}
	}
	return wFlags;
}

/**
 * This method looks up the contact's database settings for information about his timezone.
 * If it is somehow possible, the method writes all available timezone information into the
 * @e tzi structure
 *
 * @param		hContact	- the HANDLE of the contact to read timezone information for
 * @param		szProto		- contact's protocol
 * @param		tzi				- pointer to a TIME_ZONE_INFORMATION structure, which will retrieve the information
 *
 * @retval	0		- the timezone was found and successfully written to @e tzi
 * @retval	1		- there are no timezone information available for the contact
 **/
INT CTzManager::GetContactTimeZoneInformation( HANDLE hContact, LPCSTR szProto, TIME_ZONE_INFORMATION& tzi ) const
{
	if( _index.getCount() )
	{
		DWORD dwIndex;
		CTimeZone *ptz;

		ptz = GetContactTimeZone( hContact, szProto, dwIndex );

		if( !ptz )
		{
			const CHAR c = ( CHAR ) dwIndex;

			// fall back and use owners tz info for additional information
			if( c >= -24 && c <= 24 )
			{
				GetTimeZoneInformation( &tzi );
				tzi.Bias = c * 30;
				return 0;
			}
		}
		else
		{
			tzi = *ptz;
			return 0;
		}
	}
	return 1;
}
